package com.ingenico.insider.services.impl;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.security.InvalidParameterException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

import net.infonode.docking.DockingWindowListener;
import net.infonode.docking.RootWindow;
import net.infonode.docking.TabWindow;
import net.infonode.docking.properties.RootWindowProperties;
import net.infonode.docking.theme.DockingWindowsTheme;
import net.infonode.docking.util.DockingUtil;
import net.infonode.docking.util.ViewMap;
import net.infonode.docking.util.WindowMenuUtil;
import net.infonode.util.Direction;

import com.ingenico.insider.docking.DynamicView;
import com.ingenico.insider.swing.ExitAction;
import com.ingenico.insider.util.Constants;

public final class DockableViewsSupplier {
	private static Logger _logger = Logger.getLogger(DockableViewsSupplier.class.getCanonicalName());

	/**
	 * Public constant keywords
	 */
	public static final String EXIT_KEY = "exit";
	public static final String IO_MESSAGE_KEY = "io";
	public static final String NOTFOUND_MESSAGE_KEY = "notfound";

	/**
	 * singleton instance
	 */
	private static DockableViewsSupplier instance;

	/**
	 * The JFrame used to contain the docking root window, the menu and the tool bar...
	 */
	private final JFrame frame;

	/**
	 * The one and only root window
	 */
	private final RootWindow rootWindow;

	/**
	 * Contains all the views
	 */
	private final ViewMap viewMap;

	/**
	 * A map between components and the containing view
	 */
	private final HashMap<Component, DynamicView> componentMap;

	/**
	 * The currently applied docking windows theme
	 */
	private DockingWindowsTheme currentTheme;

	/**
	 * In this properties object the modified property values for close buttons etc. are stored. This object is cleared
	 * when the theme is changed.
	 */
	private final RootWindowProperties properties;

	/**
	 * The Exit Action used with menu items and buttons
	 */
	public final ExitAction exitAction;

	private void initFrameMenus() {
		final JMenu fileMenu = StandardMenuSupplier.getInstance().getFileMenu();
		final JMenuItem currentMenuItem = new JMenuItem();

		currentMenuItem.setAction(exitAction);

		fileMenu.addSeparator();
		fileMenu.add(currentMenuItem);
	}

	/**
	 * Initializes the root window properties and structure
	 */
	private void initRootWindow () {
		rootWindow.setPopupMenuFactory(WindowMenuUtil.createWindowMenuFactory(viewMap, true));

		// Set gradient theme. The theme properties object is the super object of our properties object, which
		// means our property value settings will override the theme values
		properties.addSuperObject(currentTheme.getRootWindowProperties());

		// Our properties object is the super object of the root window properties object, so all property values of the
		// theme and in our property object will be used by the root window
		rootWindow.getRootWindowProperties().addSuperObject(properties);

		// Enable the bottom window bar
		rootWindow.getWindowBar(Direction.DOWN).setEnabled(true);
	}

	/**
	 * private constructor of final class to prevent external instantiation
	 */
	private DockableViewsSupplier() {
		_logger.finest(Constants.ENTRY);

		// Create all the final internal instances
		frame = new JFrame();
		exitAction = (ExitAction) LocalisationSupplier.getInstance().localize(new ExitAction(), EXIT_KEY);
		properties = new RootWindowProperties();
		viewMap = new ViewMap();
		componentMap = new HashMap<Component, DynamicView>();

		// Create the root window based on the viewMap and initialized with an empty tab window 
		rootWindow = new RootWindow(false, viewMap, new TabWindow());

		// Initializes the current theme with a provided default theme
		currentTheme = IDWThemesSupplier.getInstance().getDefaultTheme();

		// Initializes the root window properties and structure
		_logger.fine("Initializing Root Window");
		initRootWindow();

		// Initializes the menu bar
		_logger.fine("Initializing MenuBar");
		initFrameMenus();

//		frame.getContentPane().add(toolBar, BorderLayout.NORTH);
		frame.getContentPane().add(rootWindow, BorderLayout.CENTER);
//		createMenuBar();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		_logger.finest(Constants.RETURN);
	};

	/**
	 * retrieve the singleton instance
	 *
	 * @return the DockableViewsSupplier singleton
	 */
	public static DockableViewsSupplier getInstance() {
		if (instance == null) {
			_logger.info(DockableViewsSupplier.class.getName() + " instance does not exist... Creating instance.");
			instance = new DockableViewsSupplier();
		}
		return instance;
	}

	/**
	 * Gets the current docking windows theme
	 *
	 * @return the current docking windows theme
	 * @see IDWThemesSupplier
	 */
	public DockingWindowsTheme getTheme() {
		return currentTheme;
	}
	
	/**
	 * Package method that publish a parent component to other suppliers
	 *
	 * @return a component that can be used as a parent for other components like dialogs
	 */
public	Component getComponent () {
		return frame;
	}

	/**
	 * Indicates if the window layout and window operations are currently frozen
	 * 
	 * @return true if the layout is frozen and false otherwise
	 */
	public boolean isFrozen() {
		return (
			// Window operations
				! properties.getDockingWindowProperties().getDragEnabled()
			&&	! properties.getDockingWindowProperties().getCloseEnabled()
			&&	! properties.getDockingWindowProperties().getMinimizeEnabled()
			&&	! properties.getDockingWindowProperties().getRestoreEnabled()
			&&	! properties.getDockingWindowProperties().getMaximizeEnabled()
			&&	! properties.getDockingWindowProperties().getUndockEnabled()
			&&	! properties.getDockingWindowProperties().getDockEnabled()
			// Tab reordering inside tabbed panel
			&&	! properties.getTabWindowProperties().getTabbedPanelProperties().getTabReorderEnabled()
		);
	}

	/**
	 * Freezes or unfreezes the window layout and window operations.
	 *
	 * @param freeze true for freeze, otherwise false
	 */
	public void setFrozen(final boolean freeze) {
		// Freeze window operations
		properties.getDockingWindowProperties().setDragEnabled(!freeze);
		properties.getDockingWindowProperties().setCloseEnabled(!freeze);
		properties.getDockingWindowProperties().setMinimizeEnabled(!freeze);
		properties.getDockingWindowProperties().setRestoreEnabled(!freeze);
		properties.getDockingWindowProperties().setMaximizeEnabled(!freeze);
		properties.getDockingWindowProperties().setUndockEnabled(!freeze);
		properties.getDockingWindowProperties().setDockEnabled(!freeze);

		// Freeze tab reordering inside tabbed panel
		properties.getTabWindowProperties().getTabbedPanelProperties().setTabReorderEnabled(!freeze);
	}

	/**
	 * Sets the docking windows theme.
	 *
	 * @param theme the docking windows theme
	 */
	public void setTheme(final DockingWindowsTheme theme) {
		// Clear the modified properties values
		properties.getMap().clear(true);

		properties.replaceSuperObject(
				currentTheme.getRootWindowProperties(),
				theme.getRootWindowProperties()
		);
		currentTheme = theme;
	}

	public void loadLayout(final String filename) {
		try {
			FileInputStream fis = new FileInputStream(filename);
			loadLayout(fis);
			fis.close();
		} catch (IOException e) {
//			final String message = MessageFormat.format(Constants.RUNTIME_ERROR_FORMAT, e);
			_logger.log(Level.SEVERE, e.getMessage(), e);
			MessageBoxSupplier.getInstance().showErrorDialog(IO_MESSAGE_KEY);
		}
	}

	public void loadLayout(final File file) {
		try {
			FileInputStream fis = new FileInputStream(file);
			loadLayout(fis);
			fis.close();
			_logger.info("Restored layout from " + file);
		} catch (FileNotFoundException fnfe) {
			_logger.log(Level.WARNING, fnfe.getMessage(), fnfe);
			MessageBoxSupplier.getInstance().showErrorDialog(NOTFOUND_MESSAGE_KEY, file);
		} catch (IOException ioe) {
			_logger.log(Level.SEVERE, ioe.getMessage(), ioe);
			MessageBoxSupplier.getInstance().showErrorDialog(IO_MESSAGE_KEY, ioe);
		}
	}

	public void loadLayout(final InputStream stream) throws IOException {
		try {
			// Load the layout from a byte array
			ObjectInputStream in = new ObjectInputStream(stream);
			rootWindow.read(in, true);
			in.close();
		} catch (IOException ioe) {
			_logger.log(Level.SEVERE, ioe.getMessage(), ioe);
			MessageBoxSupplier.getInstance().showErrorDialog(IO_MESSAGE_KEY, ioe);
		}
	}

	/**
	 * Convenience method to save the current docking windows layout
	 *
	 * @param filename the name of the file that will be used to store the layout
	 */
	public void saveLayout(final String filename) {
		try {
			FileOutputStream fos = new FileOutputStream(filename);
			saveLayout(fos);
			fos.close();
		} catch (IOException ioe) {
			_logger.log(Level.SEVERE, ioe.getMessage(), ioe);
			MessageBoxSupplier.getInstance().showErrorDialog(IO_MESSAGE_KEY, ioe);
		}
	}

	public void saveLayout(final File file) {
		try {
			FileOutputStream fos = new FileOutputStream(file);
			saveLayout(fos);
			fos.close();
			_logger.info("Saved layout to " + file);
		} catch (IOException ioe) {
			_logger.log(Level.SEVERE, ioe.getMessage(), ioe);
			MessageBoxSupplier.getInstance().showErrorDialog(IO_MESSAGE_KEY, ioe);
		}		
	}

	public void saveLayout(final OutputStream stream) {
		try {
			ObjectOutputStream out = new ObjectOutputStream(stream);
			rootWindow.write(out, false);
			out.close();
		} catch (IOException ioe) {
			_logger.log(Level.SEVERE, ioe.getMessage(), ioe);
			MessageBoxSupplier.getInstance().showErrorDialog(IO_MESSAGE_KEY, ioe);
		}
	}

	public void restoreView(DynamicView view) {
		// If the view is already docked but not visible,
		// we restore its focus otherwise we add it to the dock
		if (view.getRootWindow() != null) {
			_logger.finer("View is already docked... Restoring focus.");
			view.restoreFocus();
		} else {
			_logger.finer("View is not visible... Adding view to docking windows");
			DockingUtil.addWindow(view, rootWindow);
		}
	}

	public Iterator<DynamicView> getViewsIterator() {
		
		return new Iterator<DynamicView> () {
			final int viewCount = viewMap.getViewCount();
			int index = 0;

			@Override
			public boolean hasNext() {
				return (index < viewCount);
			}

			@Override
			public DynamicView next() {
				return (DynamicView)viewMap.getViewAtIndex(index++);
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();
			}
		};
	}

	public void restoreComponentById (final int id) throws InvalidParameterException {
		final DynamicView view = (DynamicView) viewMap.getView(id);

		if (view == null) {
			final String message = "Component id \"" + id + "\" was not found.";
			_logger.severe(message);
			throw new InvalidParameterException(message);
		} else {
			restoreView(view);
			_logger.finer("Restored view " + view);
		}
	}

	public Component add (final Component component, final String key) {
		DynamicView view = null;

		// We shall not add the same component twice
		if ( ! componentMap.containsKey(component)) {
			_logger.finer("Component was not found... Creating a new view.");
			view = (DynamicView) LocalisationSupplier.getInstance().localize(new DynamicView (component), key);
			viewMap.addView(view.getViewId(), view);
			componentMap.put(component, view);
		} else {
			_logger.finer("Component already registered... Fetching its value.");
			view = componentMap.get(component);
		}

		_logger.fine("Adding view " + view);
		restoreView(view);

		return component;
	}

	public void close() {
		frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
	}

	public void setJMenuBar(JMenuBar menubar) {
		frame.setJMenuBar(menubar);
	}

	public void setExtendedState (int state) {
		frame.setExtendedState(state);
	}

	public void setSize(final Dimension d) {
		frame.setSize(d);
	}

	public void setSize(final int width, final int height) {
		frame.setSize(width, height);
	}

	public void setTitle(final String title){
		frame.setTitle(title);
	}

	public void setIconImage(ImageIcon image){
		frame.setIconImage(image.getImage());
	}

	public void setVisible(final boolean visible) {
		frame.setVisible(visible);
	}

	public void addDockingWindowListener(DockingWindowListener listener) {
		rootWindow.addListener(listener);
	}

	public void removeDockingWindowListener(DockingWindowListener listener) {
		rootWindow.removeListener(listener);
	}
}
